/*
 *	DataBase Pool
 *	version 1.0 : suport DataBase that type is SQL, but which you can change just only mofidfy smaller part of code
 *	Data: 2016-03-11
 *	Where:	HOME
 *	Author: threadfly
 */

package DBPool

import (
	"database/sql"
	"errors"
	"fmt"
	"time"
)

/*
 *	DBQuery
 */
type DBQuery interface {
	DBName() string
	Process(*sql.DB)
}

/*
 *	DBResult
 */
type DBResult struct {
}

/*
 *	DBTask
 */
func NewDBTask(db *sql.DB) *DBTask {
	return &DBTask{
		db:       db,
		closChan: make(chan struct{}),
		isOver:   make(chan struct{}),
	}
}

type DBTask struct {
	db        *sql.DB
	closeChan chan struct{}
	isOver    chan struct{}
}

func (this *DBTask) Run(channal chan DBQuery) {
	defer this.clean()
	for {
		select {
		case query := <-channal:
			query.Process(this.db)
		case <-this.closeChan:
			return
		}
	}
}

func (this *DBTask) clean() {
	this.db.Close()
	close(this.isOver)
}

func (this *DBTask) Stop() {
	close(this.CloseChan)
}

func (this *DBTask) IsOver() <-chan struct{} {
	return this.isover
}

/*
 *	DBPool
 */
type DBPool struct {
	dbTasks map[string][]*DBTask // key:dbname value:connections

	querys map[string]chan DBQuery

	closeChan chan struct{}
}

/*
func (this *DBPool) GetDBQueryChan(dbname string) chan DBQuery {
	if channal, exist := this.querys[dbname]; exist {
		return exist
	} else {
		return nil
	}
}*/

func (this *DBPool) Close() {
	close(this.closeChan)
}

func (this *DBPool) Run() {
	defer this.Clearn()

	timer := time.NewTicker(5 * time.Second)

	for {
		select {
		case <-timer.C:
			this.selfCheck()
		case <-this.closeChan:
			return
		}
	}

}

/*
 * check for DBPool status , and change some status
 */
func (this *DBPool) selfCheck() {
	//TODO
}

func (this *DBPool) Clearn() {
	for _, dbtask := range this.dbTasks {
		for _, task := range dbtask {
			task.Stop()
			select {
			case <-task.IsOver():
			}
		}
	}
}

/*
 * Synchronization Write: wait untile return result
 */
func NewDBPool(configfile string) (*DBPool, error) {

	DBPOOLCONFIG.LoadConfig(configfile)

	dbpool := &DBPool{
		DBCoonM: make(map[string][]*DBTask),
	}

	for _, author := range DBPOOLCONFIG.Authors {
		dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8", author.User,
			author.Password,
			author.Address,
			author.Port,
			author.Name)

		dbpool.DBConnM[author.Name] = make([]*DBTask, 0, DBPOOLCONFIG.ConnSizePerDB)
		dbpool.DBConnM[author.Name] = make(chan DBQuery, 0, DBPOOLCONFIG.QueryChanSize)

		for i := 0; i < DBPOOLCONFIG.ConnSizePerDB; i++ {
			db, err := sql.Open("mysql", dsn)
			if err != nil {
				errinfo := fmt.Sprintf("sql.Open Error, %v %v", dsn, err)
				return nil, errors.New(errinfo)
			}

			err = db.Ping()
			if err != nil {
				errinfo := fmt.Sprintf("db.Ping Error, %v %v", dsn, err)
				return nil, errors.New(errinfo)
			}

			taskDB := NewDBTask(db)
			go taskDB.Run(dbpool.DBConnM[author.Name])

			dbpool.DBConnM[author.Name][i] = NewDBTask(db)
		}
	}

	return dbpool, nil
}

func (this *DBPool) Write(query DBQuery) error {
	if taskchannel, exist := this.querys[query.DBName()]; exist {
		taskchannel <- query
		return nil
	} else {
		return errors.New("db:" + query.DBName() + " have no connection in db pool")
	}
}
